package com.speljohan.rsbot.script;

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import com.speljohan.rsbot.accessors.CachedItem;
import com.speljohan.rsbot.accessors.Character;
import com.speljohan.rsbot.accessors.Client;
import com.speljohan.rsbot.accessors.Ground;
import com.speljohan.rsbot.accessors.Item;
import com.speljohan.rsbot.accessors.NPC;
import com.speljohan.rsbot.accessors.Node;
import com.speljohan.rsbot.accessors.NodeList;
import com.speljohan.rsbot.accessors.ObjectDefinition;
import com.speljohan.rsbot.accessors.Player;
import com.speljohan.rsbot.account.Account;
import com.speljohan.rsbot.bot.Bot;
import com.speljohan.rsbot.script.wrappers.RSCharacter;
import com.speljohan.rsbot.script.wrappers.RSInterface;
import com.speljohan.rsbot.script.wrappers.RSInterfaceChild;
import com.speljohan.rsbot.script.wrappers.RSInterfaceComponent;
import com.speljohan.rsbot.script.wrappers.RSItem;
import com.speljohan.rsbot.script.wrappers.RSItemTile;
import com.speljohan.rsbot.script.wrappers.RSNPC;
import com.speljohan.rsbot.script.wrappers.RSObject;
import com.speljohan.rsbot.script.wrappers.RSPlayer;
import com.speljohan.rsbot.script.wrappers.RSTile;
import com.speljohan.rsbot.util.RandomUtils;

/**
 * Methods class, completely static for various reasons
 * 
 * A note on the rs map: only 104 by 104 chunks are loaded at a time. This area
 * loaded is referred to as the "visible" area. It may not be visible on screen
 * however. Any tiles returned are relative to the entire map not to the start
 * of this playable area so you don't need to worry about where the loaded area
 * is. This is unlike some other bots.
 * 
 * @author Speljohan + Qauters
 */
public class Methods implements Constants {
	/**
	 * The different modes a chat could be in.
	 * */
	public enum CHAT_MODE {
		VIEW, ON, FRIENDS, OFF, HIDE
	}

	private static final String[] COLOURS_STR = new String[] { "red", "green",
		"cyan", "purple", "white" };
	private static final Color[] COLOURS_CLR = new Color[] { Color.RED,
		Color.GREEN, Color.CYAN, Color.PINK, Color.WHITE };

	private static Pattern stripFormatting = Pattern.compile("\\<.+?\\>");

	// public Account account ;
	private static RSTile current = new RSTile(0, 0);

	/**
	 * Returns the distance between the two tiles.
	 * */
	public static int distanceBetween(final RSTile t1, final RSTile t2) {
		return (int) Math.hypot(t2.getX() - t1.getX(), t2.getY() - t1.getY());
	}

	/**
	 * Draws a line on the screen at the specified index. Default is green.
	 * Colours can be set with [red] and the colour starts there and continues
	 * onto the end of the line or until set otherwise.
	 * "red","green","cyan","purple","white"
	 * */
	public static void drawLine(final Graphics render, final int row,
			final String text) {
		final FontMetrics metrics = render.getFontMetrics();
		final int height = metrics.getHeight() + 4; // height + gap
		final int y = row * height + 15 + 19;
		final String[] texts = text.split("\\[");
		int xIdx = 7;
		Color cur = Color.GREEN;
		for (String t : texts) {
			for (int i = 0; i < Methods.COLOURS_STR.length; i++) {
				if (t.length() > Methods.COLOURS_STR[i].length()
						&& t.startsWith(Methods.COLOURS_STR[i])
						&& t.charAt(Methods.COLOURS_STR[i].length()) == ']') {
					t = t.substring(Methods.COLOURS_STR[i].length() + 1);
					cur = Methods.COLOURS_CLR[i];
					break;
				}
			}
			render.setColor(Color.BLACK);
			render.drawString(t, xIdx, y + 1);
			render.setColor(cur);
			render.drawString(t, xIdx, y);
			xIdx += metrics.stringWidth(t);
		}
	}

	/**
	 * Removed the stuff in html brackets <, >
	 * */
	public static String stripFomatting(final String input) {
		return Methods.stripFormatting.matcher(input).replaceAll("");
	}

	/**
	 * The singleton of skills for this client.
	 * */
	public final Skills skills = new Skills(this);

	/**
	 * The singleton of bank methods for this client.
	 * */
	public final Bank bank = new Bank(this);

	public final GrandExchange grandExchange = new GrandExchange();

	public final InputManager input = Bot.getInputManager();

	private final java.util.Random random = new java.util.Random();

	protected final Logger log = Logger.getLogger(this.getClass().getName());

	Rectangle GAMESCREEN = new Rectangle(4, 4, 512, 334);

	ArrayList<WalkerNode> nodes = new ArrayList<WalkerNode>();

	boolean mapLoaded;

	/**
	 * Searches an array of Strings to see if it has one containing a search
	 * String.
	 * 
	 * @param items
	 *            Array of Strings to check
	 * @param searchString
	 *            String to search for
	 * @return Whether the array has a String containing the search String
	 */
	private boolean arrayContains(final String[] items,
			final String searchString) {
		for (final String item : items) {
			if (item.equalsIgnoreCase(searchString)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public boolean atBankInventoryItem(final int itemID, final String txt) {
		return bank.atItem(itemID, txt);
	}

	public boolean atDoor(final RSObject object, final String direction,
			final String action) {
		if (object == null) {
			return false;
		}
		if (Bot.getClient().isMenuOpen()) {
			atMenu("Cancel");
		}
		final RSTile objectPos = object.getLocation();
		Point t = null;
		if (direction.toLowerCase().startsWith("n")) {
			t = Calculations.tileToScreen(objectPos.getX(),
					objectPos.getY() + 1, .5, .1, 40);
		} else if (direction.toLowerCase().startsWith("s")) {
			t = Calculations.tileToScreen(objectPos.getX(), objectPos.getY(),
					.5, .1, 40);
		} else if (direction.toLowerCase().startsWith("w")) {
			t = Calculations.tileToScreen(objectPos.getX(), objectPos.getY(),
					.1, .5, 40);
		} else if (direction.toLowerCase().startsWith("e")) {
			t = Calculations.tileToScreen(objectPos.getX() + 1, objectPos
					.getY(), .1, .5, 40);
		}
		if (t == null) {
			return false;
		}
		clickMouse(t, 3, 3, false);
		return atMenu(action);
	}

	public boolean atInterface(final int iface, final int child) {
		return atInterface(RSInterface.getChildInterface(iface, child));
	}

	public boolean atInterface(final int iface, final int child,
			final String actionContains) {
		return atInterface(RSInterface.getChildInterface(iface, child),
				actionContains);
	}

	public boolean atInterface(final RSInterfaceChild i) {
		if (!i.isValid()) {
			return false;
		}
		final Rectangle pos = i.getArea();
		if (pos.x == -1 || pos.y == -1 || pos.width == -1 || pos.height == -1) {
			return false;
		}
		clickMouse((int) random(pos.getMinX(), pos.getMaxX() - 10),
				(int) random(pos.getMinY(), pos.getMaxY()), true);
		return true;
	}

	public boolean atInterface(final RSInterfaceChild i,
			final String actionContains) {
		if (!i.isValid()) {
			return false;
		}
		final Rectangle pos = i.getArea();
		if (pos.x == -1 || pos.y == -1 || pos.width == -1 || pos.height == -1) {
			return false;
		}
		moveMouse((int) random(pos.getMinX(), pos.getMaxX() - 10),
				(int) random(pos.getMinY(), pos.getMaxY()));
		if (getMenuItems().get(0).toLowerCase().contains(
				actionContains.toLowerCase())) {
			clickMouse(true);
		} else {
			clickMouse(false);
			if (!atMenu(actionContains)) {
				return false;
			}
		}
		return true;
	}

	public boolean atInventoryItem(final int itemID, final String option) {
		if (getCurrentTab() != Constants.TAB_INVENTORY
				&& !RSInterface.getInterface(Constants.INTERFACE_BANK)
				.isValid()
				&& !RSInterface.getInterface(Constants.INTERFACE_STORE)
				.isValid()) {
			openTab(Constants.TAB_INVENTORY);
		}
		final int[] items = getInventoryArray();
		final java.util.List<Integer> possible = new ArrayList<Integer>();
		for (int i = 0; i < items.length; i++) {
			if (items[i] == itemID) {
				possible.add(i);
			}
		}
		if (possible.size() == 0) {
			return false;
		}
		final int idx = possible.get(random(0, possible.size()));
		final Point t = getInventoryItemPoint(idx);
		clickMouse(t, 5, 5, false);
		return atMenu(option);
	}

	/**
	 * Performs a given action. Returns true on success.
	 * 
	 * Opens the menu if the action exists and the menu isn't open. If it is
	 * open and the action does not exist clicks "Cancel".
	 * */
	public boolean atMenu(final String optionContains) {
		int idx = getMenuIndex(optionContains);
		// log.info((optionContains + " " + idx + " " + getMenuItems());
		if (idx == -1) {
			return false;
		}
		if (idx == 0 && !isMenuOpen()) {
			clickMouse(true);
			return true;
		}
		if (!isMenuOpen()) {
			clickMouse(false);
		}
		if ((idx = getMenuIndex(optionContains)) == -1) {
			idx = getMenuIndex("Cancel");
			atMenuItem(idx);
			return false;
		}

		atMenuItem(idx);
		return true;
	}

	/**
	 * Search a menu for multiple Strings and click the first occurrence that is
	 * found.
	 * 
	 * @param items
	 *            The Strings to search the menu for
	 * @return Whether or not the option was clicked
	 */
	public boolean atMenu(final String[] items) {
		for (final String target : getMenuItems()) {
			if (arrayContains(items, target)) {
				return atMenu(target);
			}
		}
		atMenu("Cancel");
		return false;
	}

	/**
	 * Left clicks at the given index.
	 * */
	public boolean atMenuItem(final int i) {
		if (!isMenuOpen()) {
			return false;
		}
		try {
			final RSTile menu = getMenuLocation();
			final int xOff = random(4, getMenuItems().get(i).length() * 4);
			final int yOff = random(21, 29) + 15 * i;
			moveMouse(menu.getX() + xOff, menu.getY() + yOff, 2, 2);
			if (!isMenuOpen()) {
				return false;
			}
			clickMouse(true);
			return true;
		} catch (final Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	public boolean atNPC(final RSNPC npc, final String action) {
		if (npc == null) {
			return false;
		}
		final RSTile tile = npc.getLocation();
		if (!tile.isValid()) {
			return false;
		}
		if (distanceTo(tile) > 5) {
			walkTileMM(tile);
		}
		return clickCharacter(npc, action);
	}

	/**
	 * Temporary - might not exist for long.
	 */
	public boolean atNpcKillaTemp(final RSNPC npc, final String action) {
		final RSTile tile = npc.getLocation();
		final RSTile randomTile = tile.randomizeTile(1, 1);
		try {
			final int hoverRand = random(8, 13);
			for (int i = 0; i < hoverRand; i++) {
				final Point screenLoc = npc.getScreenLocation();
				if (!pointOnScreen(screenLoc)
						&& getMyPlayer().getInteracting() == null) {
					walkTileMM(randomTile);
					return true;
				}

				moveMouse(screenLoc, 15, 15);

				final List<String> menuItems = getMenuItems();
				if (menuItems.isEmpty() || menuItems.size() <= 1) {
					continue;
				}
				if (menuItems.get(0).toLowerCase().contains(
						npc.getName().toLowerCase())
						&& getMyPlayer().getInteracting() == null) {
					clickMouse(true);
					return true;
				} else {
					for (int a = 1; a < menuItems.size(); a++) {
						if (menuItems.get(a).toLowerCase().contains(
								npc.getName().toLowerCase())
								&& getMyPlayer().getInteracting() == null) {
							clickMouse(false);
							return atMenu(action);
						}
					}
				}
			}

		} catch (final Exception e) {
			e.printStackTrace();
			return false;
		}
		return false;
	}

	public boolean atObject(final RSObject object, final String action) {
		return atTile(object.getLocation(), action);
	}

	/*
	 * This method was created by mike_. Do not remove this comment. Still can't
	 * believe nobody uses this shit.. yet they cry about not being able to
	 * click on trees/ropes.
	 */
	public boolean atTile(final RSTile tile, final int h, final double xd,
			final double yd, final String action) {
		try {
			final Point location = Calculations.tileToScreen(tile.getX(), tile
					.getY(), .5, .5, h);
			if (location.x == -1 || location.y == -1) {
				return false;
			}
			moveMouse(location, 3, 3);
			if (getMenuItems().get(0).toLowerCase().contains(
					action.toLowerCase())) {
				clickMouse(true);
			} else {
				clickMouse(false);
				if (!atMenu(action)) {
					return false;
				}
			}
			wait(random(500, 1000));
			while (true) {
				if (!getMyPlayer().isMoving()) {
					break;
				}
				wait(random(500, 1000));
			}
			return true;
		} catch (final Exception e) {
			return false;
		}
	}

	public boolean atTile(final RSTile tile, final String action) {
		try {
			final Point location = Calculations.tileToScreen(tile);
			if (location.x == -1 || location.y == -1) {
				return false;
			}
			moveMouse(location, 3, 3);
			return atMenu(action);
			// if
			// (getMenuItems().get(0).toLowerCase().contains(action.toLowerCase()))
			// {
			// clickMouse(true);
			// } else {
			// clickMouse(false);
			// if (!atMenu(action)) return false;
			// }
			// wait(random(500, 1000));
			// while (true) {
			// if (!getMyPlayer().isMoving()) break;
			// wait(random(500, 1000));
			// }
			// return true;
		} catch (final Exception e) {
			return false;
		}
	}

	/*
	 * This works PERFECTLY, it never misclicks a tree, ever.
	 */
	public boolean atTree(final RSObject tree, final String action) {
		final RSTile loc1 = tree.getLocation();
		@SuppressWarnings("unused")
		final RSTile loc2 = new RSTile(loc1.getX() + 1, loc1.getY());
		@SuppressWarnings("unused")
		final RSTile loc3 = new RSTile(loc1.getX(), loc1.getY() + 1);
		final RSTile loc4 = new RSTile(loc1.getX() + 1, loc1.getY() + 1);

		final Point sloc1 = Calculations.tileToScreen(loc1.getX(), loc1.getY(),
				10);
		final Point sloc2 = Calculations.tileToScreen(loc4.getX(), loc4.getY(),
				10);
		final Point screenLoc = new Point((sloc1.x + sloc2.x) / 2,
				(sloc1.y + sloc2.y) / 2);
		if (screenLoc.x == -1 || screenLoc.y == -1) {
			return false;
		}

		moveMouse(screenLoc, 3, 3);
		return atMenu(action);
	}

	public double calculateDistance(final RSTile curr, final RSTile dest) {
		return Math.sqrt((curr.getX() - dest.getX())
				* (curr.getX() - dest.getX()) + (curr.getY() - dest.getY())
				* (curr.getY() - dest.getY()));
	}

	public boolean canContinue() {
		return getContinueInterface() != null;
	}

	public boolean canReach(final Object obj, final boolean isObject) {
		if (obj instanceof RSCharacter) {
			return Calculations.canReach(((RSCharacter) obj).getLocation(),
					isObject);
		} else if (obj instanceof RSTile) {
			return Calculations.canReach((RSTile) obj, isObject);
		} else if (obj instanceof RSObject) {
			return Calculations.canReach(((RSObject) obj).getLocation(),
					isObject);
		} else if (obj instanceof Point) {
			return Calculations.canReach(new RSTile(((Point) obj).x,
					((Point) obj).y), isObject);
		}
		return false; // Couldn't reconize obj
	}

	// End Equipment

	public void castSpell(final int spell) {
		final int size = 7;
		final int index = spell - 1;
		final int row = index / size;
		final int col = index % size;
		final int x = 570 + col * 25;
		final int y = 237 + row * 25;
		openTab(Constants.TAB_MAGIC);
		clickMouse(x + random(-size, size), y + random(-size, size), true);
	}

	public boolean clickCharacter(final RSCharacter c, final String action) {
		try {
			Point screenLoc = null;
			for (int i = 0; i < 20; i++) {
				screenLoc = c.getScreenLocation();
				if (!c.isValid() || !pointOnScreen(screenLoc)) {
					log.info("Not on screen " + action);
					return false;
				}
				if (getMouseLocation().equals(screenLoc)) {
					break;
				}
				moveMouse(screenLoc);
			}
			screenLoc = c.getScreenLocation();
			if (!getMouseLocation().equals(screenLoc)) {
				return false;
			}
			final List<String> items = getMenuItems();
			if (items.size() <= 1) {
				return false;
			}
			if (items.get(0).toLowerCase().contains(action.toLowerCase())) {
				clickMouse(screenLoc, true);
				return true;
			} else {
				clickMouse(screenLoc, false);
				return atMenu(action);
			}
		} catch (final Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	public boolean clickContinue() {
		final RSInterfaceChild cont = getContinueChildInterface();
		if (cont != null && cont.isValid()) {
			return atInterface(cont);
		}
		return true;
	}

	public void clickMouse(final boolean leftClick) {
		input.clickMouse(leftClick);
	}

	public void clickMouse(final int x, final int y, final boolean leftClick) {
		clickMouse(x, y, 0, 0, leftClick);
	}

	public void clickMouse(final int x, final int y, final int width,
			final int height, final boolean leftClick) {
		moveMouse(x, y, width, height);
		wait(random(60, 450));
		clickMouse(leftClick);
	}

	public void clickMouse(final Point p, final boolean leftClick) {
		clickMouse(p.x, p.y, leftClick);
	}

	public void clickMouse(final Point p, final int x2, final int y2,
			final boolean leftClick) {
		clickMouse(p.x, p.y, x2, y2, leftClick);
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public boolean closeBank() {
		return bank.close();
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public boolean depositAllExcept(final int... items) {
		return bank.depositAllExcept(items);
	}

	private int distance(final WalkerNode startNode, final int endX,
			final int endY) {
		final int dx = startNode.x - endX;
		final int dy = startNode.y - endY;
		return (int) Math.sqrt(dx * dx + dy * dy);
	}

	public int distanceTo(final RSCharacter c) {
		return c == null ? Integer.MAX_VALUE : distanceTo(c.getLocation());
	}

	public int distanceTo(final RSObject o) {
		return o == null ? Integer.MAX_VALUE : distanceTo(o.getLocation());
	}

	public int distanceTo(final RSTile t) {
		return t == null ? Integer.MAX_VALUE : Methods.distanceBetween(
				getMyPlayer().getLocation(), t);
	}

	public boolean doWalk(final RSTile t) {
		final Point p = Calculations.worldToMinimap(t.getX(), t.getY());
		if (p.x == -1 || p.y == -1) {
			return false;
		}
		clickMouse(p, true);
		return true;
	}

	/**
	 * Drag the mouse from the current position to a certain other position.
	 * 
	 * @param x
	 *            the x coordinate to drag to
	 * @param y
	 *            the y coordinate to drag to
	 */
	public void dragMouse(final int x, final int y) {
		input.dragMouse(x, y);
	}

	/**
	 * Drag the mouse from the current position to a certain other position.
	 * 
	 * @param p
	 *            the point to drag to
	 */
	public void dragMouse(final Point p) {
		input.dragMouse(p.x, p.y);
	}

	public boolean dropAllExcept(final int... items) { // Djbeng
		int inventoryCount = getInventoryCount();
		int[] inventoryArray = getInventoryArray();
		outer: for (int off = 0; off < inventoryArray.length; off++) {
			if (inventoryArray[off] == -1) {
				continue;
			}
			for (final int item : items) {
				if (inventoryArray[off] == item) {
					continue outer;
				}
			}

			for (int tries = 0; tries < 5; tries++) {
				atInventoryItem(inventoryArray[off], "Drop");
				wait(random(500, 700));
				if (getInventoryCount() < inventoryCount) {
					break;
				}
			}
			if (getInventoryCount() >= inventoryCount) {
				// equally, otherwise
				// something really weird
				// happend :P
				return false;
			}
			inventoryArray = getInventoryArray();
			inventoryCount = getInventoryCount();
		}
		return true;
	}

	public boolean equipmentContains(final int... itemID) {
		final RSInterfaceChild equip = getEquipmentInterface();
		int count = 0;
		for (final int item : itemID) {
			if (equip.getInventoryItemCount(item) != 0) {
				count++;
			}
		}
		return count == itemID.length;
	}

	public RSObject findObject(final int... ids) {
		// Changed to find the nearest, reachable!
		// fixed, lol, getObjectAt want a real xy not this one
		RSObject cur = null;
		int dist = -1;
		for (int x = 0; x < 104; x++) {
			for (int y = 0; y < 104; y++) {
				final RSObject o = getObjectAt(x + Bot.getClient().getBaseX(),
						y + Bot.getClient().getBaseY());
				if (o != null) {
					boolean isObject = false;
					for (final int id : ids) {
						if (o.getID() == id) {
							isObject = true;
							break;
						}
					}
					if (isObject) {
						final int distTmp = getRealDistanceTo(o.getLocation(),
								true);
						if (distTmp != -1) {
							if (cur == null) {
								dist = distTmp;
								cur = o;
							} else if (distTmp < dist) {
								cur = o;
								dist = distTmp;
							}
						}
					}
				}
			}
		}
		return cur;
	}

	public RSObject findObject(final int range, final int id) {
		final int minX = getMyPlayer().getLocation().getX() - range;
		final int minY = getMyPlayer().getLocation().getY() - range;
		final int maxX = getMyPlayer().getLocation().getX() + range;
		final int maxY = getMyPlayer().getLocation().getY() + range;
		for (int x = minX; x < maxX; x++) {
			for (int y = minY; y < maxY; y++) {
				final RSObject o = getObjectAt(x, y);
				if (o != null) {
					if (o.getID() == id) {
						return o;
					}
				}
			}
		}
		return null;
	}

	public RSObject findObject(final int range, final int[] ids) {
		final int minX = getMyPlayer().getLocation().getX() - range;
		final int minY = getMyPlayer().getLocation().getY() - range;
		final int maxX = getMyPlayer().getLocation().getX() + range;
		final int maxY = getMyPlayer().getLocation().getY() + range;
		for (int x = minX; x < maxX; x++) {
			for (int y = minY; y < maxY; y++) {
				final RSObject o = getObjectAt(x, y);
				if (o != null) {
					for (final int id : ids) {
						if (o.getID() == id) {
							return o;
						}
					}
				}
			}
		}
		return null;
	}

	private WalkerNode[] findPath(final WalkerNode startNode,
			final WalkerNode endNode) {
		try {
			if (!mapLoaded) {
				loadMap();
			}
			final ArrayList<WalkerNode> Q = new ArrayList<WalkerNode>();
			for (int i = 0; i < nodes.size(); i++) {
				final WalkerNode thisNode = nodes.get(i);
				thisNode.distance = 999999;
				thisNode.previous = null;
				Q.add(thisNode);
			}
			startNode.distance = 0;
			while (Q.isEmpty() == false) {
				WalkerNode nearestNode = Q.get(0);
				for (int i = 0; i < Q.size(); i++) {
					final WalkerNode thisNode = Q.get(i);
					if (thisNode.distance < nearestNode.distance) {
						nearestNode = thisNode;
					}
				}
				Q.remove(Q.indexOf(nearestNode));
				if (nearestNode == endNode) {
					Q.clear();
				} else {
					for (int i = 0; i < nearestNode.neighbours.size(); i++) {
						final WalkerNode neighbourNode = nearestNode.neighbours
						.get(i);
						final int alt = nearestNode.distance
						+ nearestNode.distance(neighbourNode);
						if (alt < neighbourNode.distance) {
							neighbourNode.distance = alt;
							neighbourNode.previous = nearestNode;
						}
					}
				}
			}

			final ArrayList<WalkerNode> nodePath = new ArrayList<WalkerNode>();
			nodePath.add(endNode);
			WalkerNode previousNode = endNode.previous;
			while (previousNode != null) {
				nodePath.add(previousNode);
				previousNode = previousNode.previous;
			}

			if (nodePath.size() == 1) {
				return null;
			}

			final WalkerNode[] nodeArray = new WalkerNode[nodePath.size()];
			for (int i = nodePath.size() - 1; i >= 0; i--) {
				nodeArray[nodePath.size() - i - 1] = nodePath.get(i);
			}
			return nodeArray;
		} catch (final Exception e) {
			log("GenerateProperPath: error");
		}
		return null;
	}

	public RSTile[] fixPath(final RSTile[] path) {
		final ArrayList<RSTile> newPath = new ArrayList<RSTile>();
		for (int i = 0; i < path.length - 1; i++) {
			final RSTile[] temp1 = fixPath2(path[i], path[i + 1]);
			for (final RSTile element : temp1) {
				newPath.add(element);
			}
		}
		return newPath.toArray(new RSTile[newPath.size()]);
	}

	public RSTile[] fixPath2(int startX, int startY, final int destinationX,
			final int destinationY) { // most credits to aftermath
		double dx, dy;
		final ArrayList<RSTile> list = new ArrayList<RSTile>();

		list.add(new RSTile(startX, startY));
		while (Math.hypot(destinationY - startY, destinationX - startX) > 8) {
			dx = destinationX - startX;
			dy = destinationY - startY;
			final int gamble = random(14, 17);
			while (Math.hypot(dx, dy) > gamble) {
				dx *= .95;
				dy *= .95;
			}
			startX += (int) dx;
			startY += (int) dy;
			list.add(new RSTile(startX, startY));
		}
		list.add(new RSTile(destinationX, destinationY));
		return list.toArray(new RSTile[list.size()]);

	}

	public RSTile[] fixPath2(final RSTile tile) {
		return fixPath2(getMyPlayer().getLocation().getX(), getMyPlayer()
				.getLocation().getY(), tile.getX(), tile.getY());
	}

	public RSTile[] fixPath2(final RSTile tile, final RSTile tile2) {
		return fixPath2(tile.getX(), tile.getY(), tile2.getX(), tile2.getY());
	}

	public RSTile[] generateFixedPath(final int x, final int y) {
		return fixPath(generateProperPath(x, y));
	}

	public RSTile[] generateFixedPath(final RSTile t) {
		return fixPath(generateProperPath(t.getX(), t.getY()));
	}

	public RSTile[] generateProperPath(final int targetX, final int targetY) {
		// this was originally located elsewhere, where it didn't throw errors.
		// retards of the similar calibur to those who broke this method the
		// second time,
		// removed it and now everyone has to deal with a error. Isn't RSBot
		// just great?
		if (!mapLoaded) {
			loadMap();
		}
		WalkerNode startNode = null, endNode = null;
		int shortestDistance = 0;
		for (int i = 0; i < nodes.size(); i++) {
			if (startNode == null
					|| distance(nodes.get(i), getMyPlayer().getLocation()
							.getX(), getMyPlayer().getLocation().getY()) < shortestDistance) {
				startNode = nodes.get(i);
				shortestDistance = distance(nodes.get(i), getMyPlayer()
						.getLocation().getX(), getMyPlayer().getLocation()
						.getY());
			}
		}
		shortestDistance = 0;
		for (int i = 0; i < nodes.size(); i++) {
			if (endNode == null
					|| distance(nodes.get(i), targetX, targetY) < shortestDistance) {
				endNode = nodes.get(i);
				shortestDistance = distance(nodes.get(i), targetX, targetY);
			}
		}

		final WalkerNode[] nodePath = findPath(startNode, endNode);
		if (nodePath == null) {
			final RSTile[] start = {
					new RSTile(getMyPlayer().getLocation().getX(),
							getMyPlayer().getLocation().getY()),
							new RSTile(targetX, targetY) };
			return start;
		} else {
			final RSTile[] tilePath = new RSTile[nodePath.length];
			int i = 0;
			tilePath[i] = new RSTile(getMyPlayer().getLocation().getX(),
					getMyPlayer().getLocation().getY());
			i++;
			for (; i < tilePath.length - 1; i++) {
				tilePath[i] = new RSTile(nodePath[i - 1].x, nodePath[i - 1].y);
			}
			tilePath[i] = new RSTile(targetX, targetY);
			return tilePath;
		}
	}

	public RSTile[] generateProperPath(final RSTile t) {
		return generateProperPath(t.getX(), t.getY());
	}

	Account getAccount() {
		return Bot.getAccount();
	}

	/**
	 * Gets an account property except for password.
	 * */
	public String getAccountProperty(final String key) {
		if (key.toUpperCase().equals("PASSWORD")) {
			throw new SecurityException("Can't get account password.");
		}
		return getAccount().getProperty(key);
	}

	/**
	 * Returns the angle to a given RSCharacter (RSNPC or RSPlayer).
	 * 
	 * @param n
	 *            the RSCharacter
	 * @return The angle
	 */
	public int getAngleToCharacter(final RSCharacter n) {
		return getAngleToTile(n.getLocation());
	}

	/**
	 * Returns the angle to a given coordinate pair
	 * 
	 * @param x2
	 *            X coordinate
	 * @param y2
	 *            Y coordinate
	 * @return The angle
	 */
	public int getAngleToCoordinates(final int x2, final int y2) {
		final int x1 = getMyPlayer().getLocation().getX();
		final int y1 = getMyPlayer().getLocation().getY();
		final int x = x1 - x2;
		final int y = y1 - y2;
		double angle = Math.toDegrees(Math.atan2(x, y));
		if (x == 0 && y > 0) {
			angle = 180;
		}
		if (x < 0 && y == 0) {
			angle = 90;
		}
		if (x == 0 && y < 0) {
			angle = 0;
		}
		if (x < 0 && y == 0) {
			angle = 270;
		}
		if (x < 0 && y > 0) {
			angle += 270;
		}
		if (x > 0 && y > 0) {
			angle += 90;
		}
		if (x < 0 && y < 0) {
			angle = Math.abs(angle) - 180;
		}
		if (x > 0 && y < 0) {
			angle = Math.abs(angle) + 270;
		}
		if (angle < 0) {
			angle = 360 + angle;
		}
		if (angle >= 360) {
			angle -= 360;
		}
		return (int) angle;
	}

	/**
	 * Returns the angle to a given object
	 * 
	 * @param o
	 *            The RSObject
	 * @return The angle
	 */
	public int getAngleToObject(final RSObject o) {
		return getAngleToTile(o.getLocation());
	}

	/**
	 * Returns the angle to a given tile
	 * 
	 * @param t
	 *            The RSTile
	 * @return The angle
	 */
	public int getAngleToTile(final RSTile t) {
		return getAngleToCoordinates(t.getX(), t.getY());
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public RSInterface getBankInterface() {
		return bank.getInterface();
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public int[] getBankInventoryArray() {
		return bank.getItemArray();
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public int getBankInventoryCount(final int... items) {
		return bank.getCount(items);
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public Point getBankInventoryItemPoint(final int invIndex) {
		return bank.getItemPoint(invIndex);
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public int[] getBankStackSizes() {
		return bank.getStackSizes();
	}

	// Menu

	public int getCameraAngle() {
		double mapAngle = Bot.getClient().getCameraCurveX();
		mapAngle /= 2040;
		mapAngle *= 360;
		return (int) mapAngle;
	}

	public RSInterfaceChild getChildInterface(final int index,
			final int indexChild) {
		return RSInterface.getChildInterface(index, indexChild);
	}

	Client getClient() {
		return Bot.getClient();
	}

	public RSInterfaceChild getContinueChildInterface() {
		if (Bot.getClient().getInterfaceCache() == null) {
			return null;
		}
		final RSInterface[] valid = RSInterface.getAllInterfaces();
		for (final RSInterface iface : valid) {
			final int len = iface.getChildCount();
			for (int i = 0; i < len; i++) {
				final RSInterfaceChild child = iface.getChild(i);
				if (child.containsText("Click here to continue")) {
					// || child.containsAction("Click here to continue")) {
					return child;
				}
			}
		}
		return null;
	}

	public RSInterface getContinueInterface() {
		if (Bot.getClient().getInterfaceCache() == null) {
			return null;
		}
		final RSInterface[] valid = RSInterface.getAllInterfaces();
		for (final RSInterface iface : valid) {
			if (iface.containsText("Click here to continue")) {
				// || iface.containsAction("Click here to continue")) {
				return iface;
			}
		}
		return null;
	}

	public int getCurrentTab() {
		final RSInterfaceChild[] tabs = getTabButtons();

		for (int i = 0; i < tabs.length; i++) {
			// Logout button (background color is more like an texture id)
			if (i == 14 && tabs[i].getBackgroundColor() == 1203) {
				return 14;
			}

			if (tabs[i].getBackgroundColor() != -1) {
				return i < 7 ? i + 7 : i - 7;
			}
		}

		return -1; // no selected ones.
	}

	/**
	 * Gets the destination tile. Where the flag is. WARNING: This method can
	 * return null.
	 * */
	public RSTile getDestination() { // ngovil21
		if (Bot.getClient().getDestX() <= 0) {
			return null;
		}
		return new RSTile(getClient().getDestX() + getClient().getBaseX(),
				getClient().getDestY() + getClient().getBaseY());
	}

	public int getEnergy() {
		try {
			return Integer.parseInt(RSInterface.getChildInterface(750, 5)
					.getText());
		} catch (final NumberFormatException e) {
			return 0;
		}
	}

	// End Menu

	/**
	 * Gets the equipment array.
	 * 
	 * @return an array containing all equipped items
	 */
	public int[] getEquipmentArray() {
		return getEquipmentInterface().getInventory();
	}

	public int getEquipmentCount() {
		int count = 0;
		for (final int item : getEquipmentArray()) {
			if (item != -1) {
				count++;
			}
		}
		return count;
	}

	public int getEquipmentCount(final int itemID) {
		return getEquipmentInterface().getInventoryItemCount(itemID);
	}

	/**
	 * Gets the equipment interface accessor.
	 * 
	 * @return the equipment interface
	 */
	public RSInterfaceChild getEquipmentInterface() {
		return RSInterface.getChildInterface(Constants.EQUIPMENT_X,
				Constants.EQUIPMENT_Y);
	}

	// Input methods TODO: Remove and use input.*()

	public int[] getEquipmentStackArray() {
		return getEquipmentInterface().getInventoryStackSizes();
	}

	public int getFightMode() {
		return getSetting(43);
	}

	/**
	 * Returns the first (but not the closest) item found in a square within
	 * (range) away from you.
	 * */
	public RSItemTile getGroundItem(final int range) {
		final int pX = getMyPlayer().getLocation().getX();
		final int pY = getMyPlayer().getLocation().getY();
		final int minX = pX - range;
		final int minY = pY - range;
		final int maxX = pX + range;
		final int maxY = pY + range;
		for (int x = minX; x < maxX; x++) {
			for (int y = minY; y < maxY; y++) {
				final RSItemTile[] items = getGroundItemsAt(x, y);
				if (items.length > 0) {
					return items[0];
				}
			}
		}
		return null;
	}

	/**
	 * Returns the first (but not the closest) item with a specified id in the
	 * playable(visible) area.
	 * */
	public RSItemTile getGroundItemByID(final int id) {
		return getGroundItemByID(52, new int[] { id });
	}

	/**
	 * Returns the first (but not the closest) item with a specified id found in
	 * a square within (range) away from you.
	 * */
	public RSItemTile getGroundItemByID(final int range, final int id) {
		return getGroundItemByID(range, new int[] { id });
	}

	/**
	 * Returns the first (but not the closest) item with a specified ids found
	 * in a square within (range) away from you.
	 * */
	public RSItemTile getGroundItemByID(final int range, final int[] ids) {
		final int pX = getMyPlayer().getLocation().getX();
		final int pY = getMyPlayer().getLocation().getY();
		final int minX = pX - range;
		final int minY = pY - range;
		final int maxX = pX + range;
		final int maxY = pY + range;
		for (int x = minX; x <= maxX; x++) {
			for (int y = minY; y <= maxY; y++) {
				final RSItemTile[] items = getGroundItemsAt(x, y);
				for (final RSItemTile item : items) {
					final int iId = item.getItem().getID();
					for (final int id : ids) {
						if (iId == id) {
							return item;
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Returns the first (but not the closest) item with a specified id in the
	 * playable(visible) area.
	 * */
	public RSItemTile getGroundItemByID(final int[] ids) {
		return getGroundItemByID(52, ids);
	}

	/**
	 * Returns all the ground items at a tile on the current plane.
	 * */
	public RSItemTile[] getGroundItemsAt(final int x, final int y) {
		try {
			if (!isLoggedIn()) {
				return new RSItemTile[0];
			}
			final NodeList nl = Bot.getClient().getNodeListArray()[getPlane()][x
			                                                                   - Bot.getClient().getBaseX()][y
			                                                                                                 - Bot.getClient().getBaseY()];
			if (nl == null) {
				return new RSItemTile[0];
			}
			final List<RSItemTile> list = new ArrayList<RSItemTile>();
			final Node holder = nl.getHead();
			Node curNode = holder.getNext();
			while (curNode != null && curNode != holder
					&& curNode != nl.getHead()) {
				final Item node = ((CachedItem) curNode).getItem();
				list.add(new RSItemTile(x, y, new RSItem(node.getID(), node
						.getStackSize())));
				curNode = curNode.getNext();
			}
			return list.toArray(new RSItemTile[list.size()]);
		} catch (final ArrayIndexOutOfBoundsException aiobe) {
			// aiobe.printStackTrace();
			// log.info(("p:" + getPlane() + " x:" + x + " bX:" +
			// Bot.getClient().getBaseX()
			// + " y:" + y + " bY:" + Bot.getClient().getBaseY());
			return new RSItemTile[0];
		}
	}

	/**
	 * Returns all the ground items at a tile on the current plane.
	 * */
	public RSItemTile[] getGroundItemsAt(final RSTile t) {
		return getGroundItemsAt(t.getX(), t.getY());
	}

	public RSInterface getInterface(final int in) {
		return RSInterface.getInterface(in);
	}

	public RSInterfaceChild getInterface(final int index, final int indexChild) {
		return RSInterface.getChildInterface(index, indexChild);
	}

	/**
	 * Gets the inventory array.
	 * 
	 * @return an array containing all items
	 */
	public int[] getInventoryArray() {
		final RSInterfaceChild invIface = getInventoryInterface();
		if (invIface != null) {
			if (invIface.getComponents().length > 0) {
				final int len = invIface.getComponents().length;
				final int[] inv = new int[len];
				for (int i = 0; i < len; i++) {
					try {
						final RSInterfaceComponent item = invIface
						.getComponents()[i];
						inv[item.getComponentIndex()] = item.getComponentID();
					} catch (final Exception e) {
						wait(random(500, 700));
						return getInventoryArray();
					}
				}

				return inv;
			}

			return invIface.getInventory();
		}

		return new int[0]; // try to give at least null's to scripters as
		// possible!
	}

	/**
	 * Gets the count of all items in your inventory
	 * 
	 * @return the count of all inventory items
	 */
	public int getInventoryCount() {
		return getInventoryCount(false);
	}

	/**
	 * Gets the count of all items in your inventory including it stacks
	 * 
	 * @return the count of all inventory items + stacks
	 */
	public int getInventoryCount(final boolean includeStacks) {
		int count = 0;
		final int[] items = getInventoryArray();
		final int[] itemStacks = getInventoryStackArray();
		for (int off = 0; off < items.length; off++) {
			final int item = items[off];
			if (item != -1) {
				if (includeStacks) {
					count += itemStacks[off];
				} else {
					count++;
				}
			}
		}
		return count;
	}

	/**
	 * Gets the count of a specific item in your inventory.
	 * 
	 * @param itemIDs
	 *            the item id to check
	 * @return the inventory count of the specified items
	 */
	public int getInventoryCount(final int... itemIDs) {
		int total = 0;

		for (final RSItem item : getInventoryItems()) {
			if (item == null) {
				continue;
			}

			for (final int ID : itemIDs) {
				if (item.getID() == ID) {
					total += item.getStackSize();
				}
			}
		}

		return total;
	}

	public int getInventoryCountExcept(final int... ids) {
		final int[] items = getInventoryArray();
		int count = 0;
		for (final int i : items) {
			if (i == -1) {
				continue;
			}
			boolean skip = false;
			for (final int id : ids) {
				if (i == id) {
					skip = true;
					break;
				}
			}
			if (!skip) {
				count++;
			}
		}
		return count;
	}

	/**
	 * Gets the inventory interface accessor.
	 * 
	 * @return the inventory interface
	 */
	public RSInterfaceChild getInventoryInterface() {
		if (getInterface(Constants.INVENTORY_COM_X).isValid()) {
			return RSInterface.getChildInterface(Constants.INVENTORY_COM_X,
					Constants.INVENTORY_COM_Y);
		}

		return RSInterface.getChildInterface(Constants.INVENTORY_X,
				Constants.INVENTORY_Y);
	}

	public RSItem getInventoryItemByID(final int... ids) {
		final RSItem[] items = getInventoryItems();
		for (final RSItem item : items) {
			for (final int id : ids) {
				if (item.getID() == id) {
					return item;
				}
			}
		}
		return null;
	}

	public Point getInventoryItemPoint(final int invIndex) {
		final int col = invIndex % 4;
		final int row = invIndex / 4;
		final int x = 580 + col * 42;
		final int y = 228 + row * 36;
		return new Point(x, y);
	}

	public RSItem[] getInventoryItems() {
		final RSInterfaceChild invIface = getInventoryInterface();
		if (invIface != null) {
			int[] items = invIface.getInventory();
			int[] stacks = invIface.getInventoryStackSizes();

			if (invIface.getComponents().length > 0) {
				final int len = invIface.getComponents().length;
				items = new int[len];
				stacks = new int[len];
				for (int i = 0; i < len; i++) {
					try {
						final RSInterfaceComponent item = invIface
						.getComponents()[i];
						items[item.getComponentIndex()] = item.getComponentID();
						stacks[item.getComponentIndex()] = item
						.getComponentStackSize();
					} catch (final Exception e) {
						wait(random(500, 700));
						return getInventoryItems();
					}
				}
			}

			final RSItem[] rsitem = new RSItem[items.length];
			for (int i = 0; i < items.length; i++) {
				rsitem[i] = new RSItem(items[i], stacks[i]);
			}
			return rsitem;
		}

		return new RSItem[0];
	}

	// Equipment

	/**
	 * Gets the inventory stack array.
	 * 
	 * @return an array containing all item stacks
	 */
	public int[] getInventoryStackArray() {
		final RSInterfaceChild invIface = getInventoryInterface();
		if (invIface != null) {
			if (invIface.getComponents().length > 0) {
				final int len = invIface.getComponents().length;
				final int[] inv = new int[len];
				for (int i = 0; i < len; i++) {
					try {
						final RSInterfaceComponent item = invIface
						.getComponents()[i];
						inv[item.getComponentIndex()] = item
						.getComponentStackSize();
					} catch (final Exception e) {
						wait(random(500, 700));
						return getInventoryArray();
					}
				}

				return inv;
			}

			return invIface.getInventoryStackSizes();
		}

		return new int[0]; // try to give at least null's to scripters as
		// possible!
	}

	/**
	 * Gets the player's current location.
	 * */
	public RSTile getLocation() {
		return getMyPlayer().getLocation();
	}

	public int getLoginIndex() {
		return Bot.getClient().getLoginIndex();
	}

	/**
	 * Returns the first half. "Walk here" "Follow"
	 * */
	public ArrayList<String> getMenuActions() {
		final String[] options = Bot.getClient().getMenuActions();
		final int offset = Bot.getClient().getMenuOptionsCount();
		final ArrayList<String> output = new ArrayList<String>();
		for (int i = offset - 1; i >= 0; i--) {
			final String option = options[i];
			if (option != null) {
				final String text = Methods.stripFomatting(option);
				output.add(text);
			}
		}
		return output;
	}

	/**
	 * Returns the index (Start at 0) in the menu for a given action. -1 for
	 * invalid.
	 * */
	public int getMenuIndex(String optionContains) {
		optionContains = optionContains.toLowerCase();
		final java.util.List<String> actions = getMenuItems();
		for (int i = 0; i < actions.size(); i++) {
			final String action = actions.get(i);
			if (action.toLowerCase().contains(optionContains)) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Returns first half + second half. As displayed in rs.
	 * */
	public ArrayList<String> getMenuItems() {
		final String[] options = Bot.getClient().getMenuOptions();
		final String[] actions = Bot.getClient().getMenuActions();
		final int offset = Bot.getClient().getMenuOptionsCount();
		final ArrayList<String> output = new ArrayList<String>();
		for (int i = offset - 1; i >= 0; i--) {
			final String option = options[i];
			final String action = actions[i];
			if (option != null && action != null) {
				final String text = Methods.stripFomatting(action) + ' '
				+ Methods.stripFomatting(option);
				output.add(text);
			}
		}
		return output;
	}

	/**
	 * Returns the location of the menu. Returns null if not open.
	 * */
	public RSTile getMenuLocation() {
		if (!isMenuOpen()) {
			return null;
		}
		int x = Bot.getClient().getMenuX();
		int y = Bot.getClient().getMenuY();
		x += 4;
		y += 4;
		return new RSTile(x, y);
	}

	// Inventory

	/**
	 * Returns the second half. "<user name>".
	 * */
	public ArrayList<String> getMenuOptions() {
		final String[] options = Bot.getClient().getMenuOptions();
		final int offset = Bot.getClient().getMenuOptionsCount();
		final ArrayList<String> output = new ArrayList<String>();
		for (int i = offset - 1; i >= 0; i--) {
			final String option = options[i];
			if (option != null) {
				final String text = Methods.stripFomatting(option);
				output.add(text);
			}
		}
		return output;
	}

	/**
	 * Gets the location of the Mouse in the Screen. <br />
	 * <b>Note: </b>The X & Y coords in the Point retuned could be -1.
	 * 
	 * @return a new Point containing the Mouse X & Y coords.
	 */
	public Point getMouseLocation() {
		return new Point(input.getX(), input.getY());
	}

	/**
	 * Get the mouse speed for this instance. This should be overrided if a
	 * change in speed is desired.
	 * 
	 * @see {@link #moveMouse(int, int, int, int, int)}
	 * @return the mouse speed to use for all operations.
	 */
	protected int getMouseSpeed() {
		return MouseHandler.DEFAULT_MOUSE_SPEED;
	}

	public RSPlayer getMyPlayer() {
		return new RSPlayer(Bot.getClient().getMyPlayer());
	}

	public RSNPC getNearestFreeNPCByID(final int... ids) {
		int Dist = 20;
		RSNPC closest = null;
		final int[] validNPCs = Bot.getClient().getNPCIndexArray();
		final NPC[] npcs = Bot.getClient().getNPCArray();

		for (final int element : validNPCs) {
			if (npcs[element] == null) {
				continue;
			}
			final RSNPC Monster = new RSNPC(npcs[element]);
			try {
				for (final int id : ids) {
					if (id != Monster.getID() || Monster.isInCombat()) {
						continue;
					}
					final int distance = distanceTo(Monster);
					if (distance < Dist) {
						Dist = distance;
						closest = Monster;
					}
				}
			} catch (final Exception e) {
				// e.printStackTrace(); TODO: Why?
			}
		}
		return closest;
	}

	public RSNPC getNearestFreeNPCByName(final String... names) {
		int Dist = 20;
		RSNPC closest = null;
		final int[] validNPCs = Bot.getClient().getNPCIndexArray();
		final NPC[] npcs = Bot.getClient().getNPCArray();

		for (final int element : validNPCs) {
			if (npcs[element] == null) {
				continue;
			}
			final RSNPC Monster = new RSNPC(npcs[element]);
			try {
				for (final String name : names) {
					if (name == null || !name.equals(Monster.getName())
							|| Monster.isInCombat()) {
						continue;
					}
					final int distance = distanceTo(Monster);
					if (distance < Dist) {
						Dist = distance;
						closest = Monster;
					}
				}
			} catch (final Exception e) {
				e.printStackTrace();
			}
		}
		return closest;
	}

	public RSNPC getNearestNPCByID(final int... ids) {
		int Dist = 20;
		RSNPC closest = null;
		final int[] validNPCs = Bot.getClient().getNPCIndexArray();
		final NPC[] npcs = Bot.getClient().getNPCArray();

		for (final int element : validNPCs) {
			if (npcs[element] == null) {
				continue;
			}
			final RSNPC Monster = new RSNPC(npcs[element]);
			try {
				for (final int id : ids) {
					if (id != Monster.getID()) {
						continue;
					}
					final int distance = distanceTo(Monster);
					if (distance < Dist) {
						Dist = distance;
						closest = Monster;
					}
				}
			} catch (final Exception e) {
				// e.printStackTrace(); TODO: Why?
			}
		}
		return closest;
	}

	public RSNPC getNearestNPCByName(final String... names) {
		int Dist = 20;
		RSNPC closest = null;
		final int[] validNPCs = Bot.getClient().getNPCIndexArray();
		final NPC[] npcs = Bot.getClient().getNPCArray();

		for (final int element : validNPCs) {
			if (npcs[element] == null) {
				continue;
			}
			final RSNPC Monster = new RSNPC(npcs[element]);
			try {
				for (final String name : names) {
					if (!name.equals(Monster.getName())) {
						continue;
					}
					final int distance = distanceTo(Monster);
					if (distance < Dist) {
						Dist = distance;
						closest = Monster;
					}
				}
			} catch (final Exception e) {
				e.printStackTrace();
			}
		}
		return closest;
	}

	public RSObject getNearestObjectByID(final int... ids) {
		RSObject cur = null;
		double dist = -1;
		for (int x = 0; x < 104; x++) {
			for (int y = 0; y < 104; y++) {
				final RSObject o = getObjectAt(x + Bot.getClient().getBaseX(),
						y + Bot.getClient().getBaseY());
				if (o != null) {
					boolean isObject = false;
					for (final int id : ids) {
						if (o.getID() == id) {
							isObject = true;
							break;
						}
					}
					if (isObject) {
						final double distTmp = calculateDistance(getMyPlayer()
								.getLocation(), o.getLocation());
						if (cur == null) {
							dist = distTmp;
							cur = o;
						} else if (distTmp < dist) {
							cur = o;
							dist = distTmp;
						}
					}
				}
			}
		}
		return cur;
	}

	public RSPlayer getNearestPlayerByLevel(final int level) {
		int Dist = 20;
		RSPlayer closest = null;
		final int[] validPlayers = Bot.getClient().getPlayerIndexArray();
		final Player[] players = Bot.getClient().getPlayerArray();

		for (final int element : validPlayers) {
			if (players[element] == null) {
				continue;
			}
			final RSPlayer player = new RSPlayer(players[element]);
			try {
				if (level != player.getCombatLevel()) {
					continue;
				}
				final int distance = distanceTo(player);
				if (distance < Dist) {
					Dist = distance;
					closest = player;
				}
			} catch (final Exception e) {
				// e.printStackTrace(); TODO: Why?
			}
		}
		return closest;
	}

	public RSPlayer getNearestPlayerByLevel(final int min, final int max) {
		int Dist = 20;
		RSPlayer closest = null;
		final int[] validPlayers = Bot.getClient().getPlayerIndexArray();
		final Player[] players = Bot.getClient().getPlayerArray();

		for (final int element : validPlayers) {
			if (players[element] == null) {
				continue;
			}
			final RSPlayer player = new RSPlayer(players[element]);
			try {
				if (player.getCombatLevel() < min
						&& player.getCombatLevel() > max) {
					continue;
				}
				final int distance = distanceTo(player);
				if (distance < Dist) {
					Dist = distance;
					closest = player;
				}
			} catch (final Exception e) {
				// e.printStackTrace(); TODO: Why?
			}
		}
		return closest;
	}

	public RSPlayer getNearestPlayerByName(final String name) {
		int Dist = 20;
		RSPlayer closest = null;
		final int[] validPlayers = Bot.getClient().getPlayerIndexArray();
		final Player[] players = Bot.getClient().getPlayerArray();

		for (final int element : validPlayers) {
			if (players[element] == null) {
				continue;
			}
			final RSPlayer player = new RSPlayer(players[element]);
			try {
				if (!name.equals(player.getName())) {
					continue;
				}
				final int distance = distanceTo(player);
				if (distance < Dist) {
					Dist = distance;
					closest = player;
				}
			} catch (final Exception e) {
				// e.printStackTrace(); TODO: Why?
			}
		}
		return closest;
	}

	public RSObject getObjectAt(final int x, final int y) {
		if (Bot.getClient().getGroundArray() == null) {
			return null;
		}
		for (int i = 0; i < 4; i++) {
			try {
				final Ground ground = Bot.getClient().getGroundArray()[i][x
				                                                          - Bot.getClient().getBaseX()][y
				                                                                                        - Bot.getClient().getBaseY()];
				if (ground != null) {
					if (ground.getObject5Array() != null) {
						for (final ObjectDefinition obj : ground
								.getObject5Array()) {
							if (obj != null && obj.getUID() != -1L) {
								return new RSObject(obj.getUID());
							}
						}
					}
					if (ground.getObject4() != null
							&& ground.getObject4().getUID() != -1L) {
						return new RSObject(ground.getObject4().getUID());
					}
					if (ground.getObject3() != null
							&& ground.getObject3().getUID() != -1L) {
						return new RSObject(ground.getObject3().getUID());
					}
					if (ground.getObject2() != null
							&& ground.getObject2().getUID() != -1L) {
						return new RSObject(ground.getObject2().getUID());
					}
					if (ground.getObject1() != null
							&& ground.getObject1().getUID() != -1L) {
						return new RSObject(ground.getObject1().getUID());
					}
				}
			} catch (final Exception e) {
				// e.printStackTrace(); TODO: Why?
			}
		}
		return null;
	}

	public RSObject getObjectAt(final RSTile t) {
		return getObjectAt(t.getX(), t.getY());
	}

	/**
	 * Gets the plane we are currently on.
	 * */
	public int getPlane() {
		return Bot.getClient().getPlane();
	}

	// Calculations
	public int getRealDistanceTo(final RSTile t, final boolean isObject) {
		final RSTile curPos = getMyPlayer().getLocation();
		return Calculations.getRealDistanceTo(curPos.getX()
				- Bot.getClient().getBaseX(), // startX
				curPos.getY() - Bot.getClient().getBaseY(), // startY
				t.getX() - Bot.getClient().getBaseX(), // destX
				t.getY() - Bot.getClient().getBaseY(), // destY
				isObject); // if it's an object, calculate path to it
	}

	/**
	 * Returns the currently selected item in your inventory.
	 * 
	 * @return the id of your currently selected inventory item
	 */
	public int getSelectedItemID() {
		return Bot.getClient().getItemSelected();
	}

	public int getSetting(final int setting) {
		final int[] settings = Bot.getClient().getSettingArray();
		if (settings != null && setting < settings.length) {
			return settings[setting];
		}
		return -1;
	}

	// End Inventory

	// Bank

	public int[] getSettingArray() {
		final int[] settings = Bot.getClient().getSettingArray();
		if (settings == null) {
			return new int[0];
		}
		return settings.clone(); // NEVER return pointer
	}

	private RSInterfaceChild[] getTabButtons() {
		final RSInterfaceChild[] interfaces = new RSInterfaceChild[15];
		final RSInterface invInterface = RSInterface.getInterface(548);
		int cTab = 0;
		for (int i = 21; i < 46; i++) {
			if (i < 28 || i > 38) {
				interfaces[cTab] = invInterface.getChild(i);
				cTab++;
			}
		}
		interfaces[14] = invInterface.getChild(70);// logout button
		return interfaces;
	}

	public RSInterfaceChild getTalkInterface() {
		for (final int talk : Constants.INTERFACE_TALKS) {
			final RSInterfaceChild child = RSInterface.getChildInterface(talk,
					0);
			if (child.isValid()) {
				return child;
			}
		}
		return null;
	}

	@Deprecated
	public RSTile getTile(final Character character) {
		return new RSTile(Bot.getClient().getBaseX() + (character.getX() >> 7),
				Bot.getClient().getBaseY() + (character.getY() >> 7));
	}

	/**
	 * DONT use this method or I will tear your heart out and make you eat it.
	 * Use RSInterface.getInterface(id).isValid().
	 * 
	 * @param id
	 *            Interface ID
	 * @return If the interface exists.
	 */
	public boolean interfaceExists(final int id) {
		return RSInterface.getInterface(id).isValid();
	}

	/**
	 * Checks if your inventory contains the specific items.
	 * 
	 * @param itemID
	 *            the item(s) you wish to evaluate
	 * @return true if your inventory contains the item id(s)
	 */
	public boolean inventoryContains(final int... itemID) {
		for (final int i : itemID) {
			if (getInventoryItemByID(i) == null) {
				return false;
			}
		}
		return true;
	}

	public boolean inventoryContainsOneOf(final int... itemID) {
		final int[] items = getInventoryArray();
		for (final int item : items) {
			for (final int i : itemID) {
				if (item == i) {
					return true;
				}
			}
		}
		return false;
	}

	public boolean isCarryingItem(final int... items) {
		return equipmentContains(items) || inventoryContains(items);
	}

	/**
	 * This method returns true if the player is currently idle.
	 */
	public boolean isIdle() {
		return !getMyPlayer().isMoving() && getMyPlayer().getAnimation() == -1;
	}

	/**
	 * Checks if your inventory is full.
	 * 
	 * @return true if your inventory is full
	 */
	public boolean isInventoryFull() {
		return getInventoryCount() == 28;
	}

	/**
	 * Gets login status.
	 * 
	 * @return true if logged in
	 */
	public boolean isLoggedIn() {
		final Client client = Bot.getClient();
		final int index = client == null ? -1 : client.getLoginIndex();
		return index == 30 || index == 25;
	}

	/**
	 * Returns true if we are at the login screen.
	 * */
	public boolean isLoginScreen() {
		return Bot.getClient().getLoginIndex() == 10;
	}

	/**
	 * Returns true if the menu is open.
	 * */
	public boolean isMenuOpen() {
		return Bot.getClient().isMenuOpen();
	}

	public boolean isRetaliateEnabled() {
		return getSetting(172) == 0;
	}

	// End Bank

	// Start Interfaces

	public boolean isRunning() {
		return getSetting(173) == 1;
	}

	private void loadLinks() {
		try {
			final BufferedReader reader = new BufferedReader(new FileReader(
			"Settings\\WalkerLinks.txt"));
			String line = null;
			String[] lineSplit;
			while ((line = reader.readLine()) != null) {
				lineSplit = line.split("\t");
				WalkerNode node = nodes.get(Integer.parseInt(lineSplit[0]));
				node.neighbours.add(nodes.get(Integer.parseInt(lineSplit[1])));
				node = nodes.get(Integer.parseInt(lineSplit[1]));
				node.neighbours.add(nodes.get(Integer.parseInt(lineSplit[0])));
			}
			reader.close();
		} catch (final Exception e) {
		}
	}

	private void loadMap() {
		mapLoaded = true;
		loadNodes();
		loadLinks();
	}

	private void loadNodes() {
		try {
			final BufferedReader reader = new BufferedReader(new FileReader(
			"Settings\\WalkerNodes.txt"));
			String line = null;
			String[] lineSplit;
			while ((line = reader.readLine()) != null) {
				lineSplit = line.split("\t");
				nodes.add(new WalkerNode(Integer.parseInt(lineSplit[0]),
						Integer.parseInt(lineSplit[1])));
			}
			reader.close();
		} catch (final Exception e) {
		}
	}

	protected void log(final String message) {
		log.info(message);
	}

	public boolean login() {
		if (isLoggedIn()) {
			return true;
		}
		if (!isLoginScreen()) {
			return false;
		}
		for (int i = 0; i < 5; i++) {
			if (getLoginIndex() == 10) {
				// clickMouse(300, 165, 146, 14, true);
				input.sendKeys("", true);
				wait(random(50, 200));
				final Account account = getAccount();
				input.sendKeys(account.getUsername(), true);
				wait(random(100, 400));
				input.sendKeys(account.getPassword(), true);
				// clickMouse(300, 360, 146, 14, true);
			}
			if (getLoginIndex() == 30) {
				break;
			}
			wait(random(500, 1000));
		}
		return getLoginIndex() == 30;
	}

	// End interfaces

	public boolean logout() {
		// atInterface(548, 91);
		clickMouse(752, 6, 12, 17, true);
		wait(random(500, 1500));
		if (RSInterface.getInterface(548).isValid()) {
			clickMouse(578, 366, 120, 20, true);
			// atInterface(182, 6);
			wait(random(1000, 1500));
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Click chat button. Saves space, actually works.
	 * 
	 * @param button
	 *            Which button? Left-to right, 0 to 6. 7 Would land you on
	 *            Report Abuse.
	 * @param left
	 *            Left or right button? Left = true. Right = false;
	 */
	public void mouseChatButton(final int button, final boolean left) {
		int x = Constants.CHAT_BUTTON_CENTER_X + Constants.CHAT_BUTTON_DIFF_X
		* button;
		x = random(x - Constants.CHAT_BUTTON_MAX_DX, x
				+ Constants.CHAT_BUTTON_MAX_DX);
		int y = Constants.CHAT_BUTTON_CENTER_Y;
		y = random(y - Constants.CHAT_BUTTON_MAX_DY, y
				+ Constants.CHAT_BUTTON_MAX_DY);
		moveMouse(x, y);
		wait(random(200, 300));
		clickMouse(left);
	}

	public void moveMouse(final int x, final int y) {
		moveMouse(x, y, 0, 0);
	}

	/**
	 * @see #moveMouse(int, int, int, int, int)
	 * @see #getMouseSpeed
	 */
	public void moveMouse(final int x, final int y, final int randX,
			final int randY) {
		input.moveMouse(getMouseSpeed(), x, y, randX, randY);
	}

	/**
	 * Moves the mouse to the specified point at a certain sped.
	 * 
	 * @param speed
	 *            the lower, the faster.
	 * @param x
	 *            the x value
	 * @param y
	 *            the y value
	 * @param randX
	 *            x-axis randomness (gets added to x)
	 * @param randY
	 *            y-axis randomness (gets added to y)
	 */
	public void moveMouse(final int speed, final int x, final int y,
			final int randX, final int randY) {
		input.moveMouse(speed, x, y, randX, randY);
	}

	public void moveMouse(final Point p) {
		moveMouse(p.x, p.y, 0, 0);
	}

	public void moveMouse(final Point p, final int randX, final int randY) {
		moveMouse(p.x, p.y, randX, randY);
	}

	public RSTile nextTile(final RSTile[] path, final int maxDist) {
		final RSTile cur = getMyPlayer().getLocation();
		for (int i = path.length - 1; i >= 0; i--) {
			if (Methods.distanceBetween(cur, path[i]) <= maxDist
					&& Methods.distanceBetween(cur, path[path.length - 1]) > 5) {
				return path[i];
			}
		}
		return null;
	}

	public void onFinish() {
		stopAllScripts();
	}

	public void openTab(final int tab) {
		if (tab == getCurrentTab()) {
			return;
		}
		final RSInterfaceChild[] tabs = getTabButtons();
		int id = tab;
		if (id >= 7 && id != 14) {
			id -= 7;
		} else if (id != 14) {
			id += 7;
		}
		atInterface(tabs[id]);
	}

	public boolean pointOnScreen(final Point check) {
		return GAMESCREEN.contains(check.x, check.y);
	}

	/**
	 * Returns a random double in a specified range
	 * 
	 * @param min
	 *            Minimum value. (inclusive)
	 * @param max
	 *            Maximum value. (exclusive)
	 * @return The random double generated.
	 */
	public double random(final double min, final double max) {
		return min + random.nextDouble() * (max - min);
	}

	/**
	 * Returns a random integer in a specified range
	 * 
	 * @param min
	 *            Minimum value. (inclusive)
	 * @param max
	 *            Maximum value. (exclusive)
	 * @return The random integer generated.
	 */
	public int random(final int min, final int max) {
		return RandomUtils.randomInt(random, min, max);
	}

	public Point randomiseInventoryItemPoint(final Point inventoryPoint) {
		return new Point(inventoryPoint.x + random(-10, 10), inventoryPoint.y
				+ random(-10, 10));
	}

	/**
	 * Randomize a path of tiles.
	 * 
	 * @param path
	 *            The RSTiles to randomize.
	 * @param maxXDeviation
	 *            Max X distance from tile x.
	 * @param maxYDeviation
	 *            Max Y distance from tile y.
	 * @return a new, randomised path.
	 */
	public RSTile[] randomizePath(final RSTile[] path, final int maxXDeviation,
			final int maxYDeviation) {
		final RSTile[] rez = new RSTile[path.length];
		for (int i = 0; i < path.length; i++) {
			rez[i] = randomizeTile(path[i], maxXDeviation, maxYDeviation);
		}
		return rez;
	}

	/**
	 * Randomize a single tile. Because I got tired of having to make one-tile
	 * paths.
	 * 
	 * @param tile
	 *            Tile to randomize.
	 * @param maxXDeviation
	 *            Max X distance from tile x.
	 * @param maxYDeviation
	 *            Max Y distance from tile y.
	 * @return The randomized tile
	 */
	public RSTile randomizeTile(final RSTile tile, final int maxXDeviation,
			final int maxYDeviation) {
		int x = tile.getX();
		int y = tile.getY();
		if (maxXDeviation > 0) {
			double d = random.nextDouble() * 2;
			d -= 1.0;
			d *= maxXDeviation;
			x += (int) d;
		}
		if (maxYDeviation > 0) {
			double d = random.nextDouble() * 2;
			d -= 1.0;
			d *= maxYDeviation;
			y += (int) d;
		}
		return new RSTile(x, y);
	}

	public RSTile[] reversePath(final RSTile[] other) {
		final RSTile[] t = new RSTile[other.length];
		for (int i = 0; i < t.length; i++) {
			t[i] = other[other.length - i - 1];
		}
		return t;
	}

	/**
	 * Runs a script by name with the given arguments.
	 * 
	 * @param scriptName
	 *            The script name to run (case-sensitive)
	 * @param arguments
	 *            The arguments with which to run the script
	 */
	public void runScript(final String scriptName,
			final Map<String, String> arguments) {
		final ScriptHandler sh = Bot.getScriptHandler();
		sh.runScript(sh.getScriptForName(scriptName), arguments);
	}

	/**
	 * Searches for an item in the bank. Returns true if succeeded (does not
	 * necessarily means found)
	 * 
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 * */
	@Deprecated
	public boolean searchBankItem(final String itemName) {
		return bank.searchItem(itemName);
	}

	public void sendKey(final char c) {
		input.sendKey(c);
	}

	public void sendText(final String text, final boolean pressEnter) {
		input.sendKeys(text, pressEnter);
	}

	public boolean setAssistMode(final CHAT_MODE mode) {
		if (mode.equals(CHAT_MODE.HIDE)) {
			throw new IllegalArgumentException("Bad mode: HIDE");
		}
		mouseChatButton(6, false);
		return atMenu(mode.toString());
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public boolean setBankRearrangeModeToInsert() {
		return bank.setRearrangeModeToInsert();
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public boolean setBankRearrangeModeToSwap() {
		return bank.setRearrangeModeToSwap();
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public boolean setBankWithdrawModeToItem() {
		return bank.setWithdrawModeToItem();
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public boolean setBankWithdrawModeToNote() {
		return bank.setWithdrawModeToNote();
	}

	public void setCameraAltitude(final boolean maxAltitude) {
		final char key = (char) (maxAltitude ? KeyEvent.VK_UP
				: KeyEvent.VK_DOWN);
		Bot.getInputManager().pressKey(key);
		wait(random(1000, 1500));
		Bot.getInputManager().releaseKey(key);
	}

	/**
	 * Set the camera to a certain percentage of the maximum altitude. Don't
	 * rely on the return value too much - it should return whether the camera
	 * was successfully set, but it isn't very accurate near the very extremes
	 * of the height.
	 * 
	 * This also depends on the maximum camera angle in a region, as it changes
	 * depending on situation and surroundings. So in some areas, 68% might be
	 * the maximum altitude. This method will do the best it can to switch the
	 * camera altitude to what you want, but if it hits the maximum or stops
	 * moving for any reason, it will return.
	 * 
	 * Mess around a little to find the altitude percentage you like. In later
	 * versions, there will be easier-to-work-with methods regarding altitude.
	 * 
	 * @param altPercent
	 *            The percentage of the maximum altitude to set the camera to.
	 * @return Whether the camera was successfully moved.
	 */
	public boolean setCameraAltitude(final double altPercent) {
		final int alt = (int) (altPercent / 100 * -1237 - 1226);
		int curAlt = Bot.getClient().getCamPosZ();
		int lastAlt = 0;

		if (curAlt == alt) {
			log.info("Good: " + curAlt);
			return true;
		} else if (curAlt > alt) {
			Bot.getInputManager().pressKey((char) KeyEvent.VK_UP);
			long start = System.currentTimeMillis();
			while (curAlt > alt && System.currentTimeMillis() - start < 30) {
				if (lastAlt != curAlt) {
					start = System.currentTimeMillis();
				}
				lastAlt = curAlt;

				wait(1);
				curAlt = Bot.getClient().getCamPosZ();
			}
			Bot.getInputManager().releaseKey((char) KeyEvent.VK_UP);
			return true;
		} else {
			Bot.getInputManager().pressKey((char) KeyEvent.VK_DOWN);
			long start = System.currentTimeMillis();
			while (curAlt < alt && System.currentTimeMillis() - start < 30) {
				if (lastAlt != curAlt) {
					start = System.currentTimeMillis();
				}
				lastAlt = curAlt;
				wait(1);
				curAlt = Bot.getClient().getCamPosZ();
			}
			Bot.getInputManager().releaseKey((char) KeyEvent.VK_DOWN);
			return true;
		}
	}

	/**
	 * Rotates the camera to a specific angle in the closest direction.
	 * 
	 * @param degrees
	 *            The angle to rotate to.
	 */
	public void setCameraRotation(int degrees) {
		final char left = 37;
		final char right = 39;
		char whichDir = left;
		int start = getCameraAngle();

		/*
		 * Some of this shit could be simplified, but it's easier to wrap my
		 * mind around it this way
		 */
		if (start < 180) {
			start += 360;
		}
		if (degrees < 180) {
			degrees += 360;
		}

		if (degrees > start) {
			if (degrees - 180 < start) {
				whichDir = right;
			}
		} else if (start > degrees) {
			if (start - 180 >= degrees) {
				whichDir = right;
			}
		}
		degrees %= 360;

		Bot.getInputManager().pressKey(whichDir);
		int timeWaited = 0;

		while (getCameraAngle() > degrees + 5 || getCameraAngle() < degrees - 5) {
			wait(10);
			timeWaited += 10;
			if (timeWaited > 500) {
				final int time = timeWaited - 500;

				if (time == 0) {
					Bot.getInputManager().pressKey(whichDir);
				} else if (time % 40 == 0) {
					Bot.getInputManager().pressKey(whichDir);
				}
			}
		}
		Bot.getInputManager().releaseKey(whichDir);
	}

	public boolean setClanMode(final CHAT_MODE mode) {
		if (mode.equals(CHAT_MODE.HIDE)) {
			throw new IllegalArgumentException("Bad mode: HIDE");
		}
		mouseChatButton(4, false);
		return atMenu(mode.toString());
	}

	public void setCompass(final char direction) {
		switch (direction) {
		case 'n':
			setCameraRotation(359);
			break;
		case 'w':
			setCameraRotation(89);
			break;
		case 's':
			setCameraRotation(179);
			break;
		case 'e':
			setCameraRotation(269);
			break;
		default:
			setCameraRotation(359);
		break;
		}
	}

	public void setFightMode(final int fightMode) {
		if (fightMode != getFightMode()) {
			openTab(Constants.TAB_ATTACK);
			if (fightMode == 0) {
				clickMouse(577, 253, 55, 35, true);
			} else if (fightMode == 1) {
				clickMouse(661, 253, 55, 35, true);
			} else if (fightMode == 2) {
				clickMouse(576, 306, 55, 35, true);
			} else if (fightMode == 3) {
				clickMouse(662, 308, 55, 35, true);
			}
		}
	}

	public boolean setPrivateChat(final CHAT_MODE mode) {
		if (mode.equals(CHAT_MODE.HIDE)) {
			throw new IllegalArgumentException("Bad mode: HIDE");
		}
		mouseChatButton(3, false);
		return atMenu(mode.toString());
	}

	public boolean setPublicChat(final CHAT_MODE mode) {
		mouseChatButton(2, false);
		return atMenu(mode.toString());
	}

	/**
	 * Turns run on or off using the new l33t minimap controls :3
	 * 
	 * @param enable
	 *            Turns run on if true, off if false.
	 */
	public void setRun(final boolean enable) {
		if (isRunning() == enable) {
			return;
		}

		atInterface(750, 0);
	}

	public boolean setTradeMode(final CHAT_MODE mode) {
		if (mode.equals(CHAT_MODE.HIDE)) {
			throw new IllegalArgumentException("Bad mode: HIDE");
		}
		mouseChatButton(5, false);
		return atMenu(mode.toString());
	}

	public void showAllChatMessages() {
		mouseChatButton(0, true);
	}

	public void showGameChatMessages() {
		mouseChatButton(1, true);
	}

	public void stopAllScripts() {
		log.info("All Scripts Stopped");
		logout();
		Bot.getScriptHandler().stopAllScripts();
	}

	/**
	 * Switches worlds. Because the old method was shit.
	 * 
	 * @param world
	 *            World to switch to.
	 */
	public void switchWorld(int world) {
		if (isLoggedIn()) {
			logout();
		}
		if (world > 121) {
			world -= 1;
		}

		clickMouse(random(346, 421), random(231, 243), true);
		wait(random(2000, 3000));
		clickMouse(59 + world / 24 * 93 + random(0, 93), 35 + 19
				* (world % 24 - 1) + random(0, 19), true);

		log.info("World: " + world); // wrong when world >121
	}

	public boolean tileOnMap(final RSTile t) {
		final Point p = tileToMinimap(t);
		return p != null && p.x != -1 && p.y != -1;
	}

	public boolean tileOnScreen(final RSTile t) {
		final Point p = Calculations.tileToScreen(t, 0);
		return p.getX() > 0 && p.getY() > 0;
	}

	public Point tileToMinimap(final RSTile t) {
		return worldToMinimap(t.getX(), t.getY());
	}

	/**
	 * Enable or disable a random event handler.
	 * 
	 * @param name
	 *            Name of the anti-random to disable
	 * @param active
	 *            True to enable, false to disable.
	 */
	protected void toggleRandom(final String name, final boolean active) {
		for (final Random r : Bot.getScriptHandler().getRandoms()) {
			if (r.getName().equals(name)) {
				r.isUsed = active;
			}
		}
	}

	/**
	 * Turns to an RSCharacter (RSNPC or RSPlayer).
	 * 
	 * @param c
	 *            The RSCharacter to turn to.
	 */
	public void turnToCharacter(final RSCharacter c) {
		final int angle = getAngleToCharacter(c);
		setCameraRotation(angle);
	}

	/**
	 * Turns to within a few degrees of an RSCharacter (RSNPC or RSPlayer).
	 * 
	 * @param c
	 *            The RSCharacter to turn to.
	 * @param dev
	 *            The maximum difference in the angle.
	 */
	public void turnToCharacter(final RSCharacter c, final int dev) {
		int angle = getAngleToCharacter(c);
		angle = random(angle - dev, angle + dev + 1);
		setCameraRotation(angle);
	}

	/**
	 * Turns the camera to a tile specified by x and y coordinates.
	 * 
	 * @param x
	 *            The tile x coordinate
	 * @param y
	 *            The tile y coordinate
	 */
	public void turnToCoordinates(final int x, final int y) {
		turnToTile(new RSTile(x, y));
	}

	/**
	 * Turns the camera to within a few degrees of a tile specified by x and y
	 * coordinates.
	 * 
	 * @param x
	 *            The tile x coordinate
	 * @param y
	 *            The tile y coordinate
	 * @param dev
	 *            Maximum difference in angle between actual and chosen
	 *            rotation.
	 */
	public void turnToCoordinates(final int x, final int y, final int dev) {
		turnToTile(new RSTile(x, y), dev);
	}

	/**
	 * Turns to an RSObject
	 * 
	 * @param o
	 *            The RSObject to turn to.
	 */
	public void turnToObject(final RSObject o) {
		final int angle = getAngleToObject(o);
		setCameraRotation(angle);
	}

	/**
	 * Turns to within a few degrees of an RSObject.
	 * 
	 * @param o
	 *            The RSObject to turn to.
	 * @param dev
	 *            The maximum difference in the turn angle.
	 */
	public void turnToObject(final RSObject o, final int dev) {
		int angle = getAngleToObject(o);
		angle = random(angle - dev, angle + dev + 1);
		setCameraRotation(angle);
	}

	/**
	 * Turns to a specific RSTile.
	 * 
	 * @param tile
	 *            Tile to turn to.
	 */
	public void turnToTile(final RSTile tile) {
		final int angle = getAngleToTile(tile);
		setCameraRotation(angle);
	}

	/**
	 * Turns within a few degrees to a specific RSTile.
	 * 
	 * @param tile
	 *            Tile to turn to.
	 * @param dev
	 *            Maximum deviation from the angle to the tile.
	 */
	public void turnToTile(final RSTile tile, final int dev) {
		int angle = getAngleToTile(tile);
		angle = random(angle - dev, angle + dev + 1);
		setCameraRotation(angle);
	}

	public boolean useItem(final RSItem item, final RSItem targetItem) {
		if (getCurrentTab() != Constants.TAB_INVENTORY) {
			openTab(Constants.TAB_INVENTORY);
		}
		atInventoryItem(item.getID(), "Use");
		return atInventoryItem(targetItem.getID(), "Use");
	}

	public boolean useItem(final RSItem item, final RSObject targetObject) {
		if (getCurrentTab() != Constants.TAB_INVENTORY) {
			openTab(Constants.TAB_INVENTORY);
		}
		atInventoryItem(item.getID(), "Use");
		return atObject(targetObject, "Use");
	}

	/**
	 * Pauses for a specified number of milliseconds. Try NOT to use this
	 * method.
	 * 
	 * @param toSleep
	 *            Time in milliseconds to pause
	 */
	public void wait(final int toSleep) {
		try {
			final long start = System.currentTimeMillis();
			Thread.sleep(toSleep);

			// Guarantee minimum sleep
			long now;
			while (start + toSleep > (now = System.currentTimeMillis())) {
				Thread.sleep(start + toSleep - now);
			}
		} catch (final InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Waits up to timeout millis for an animation to trigger. Will return the
	 * instant an animation begins.
	 * 
	 * @param timeout
	 *            Maximum time to wait for an animation (in milliseconds)
	 * @return The animation if an animation triggered, or -1 if there was no
	 *         animation.
	 */
	public int waitForAnim(final int timeout) {
		final long start = System.currentTimeMillis();
		final RSPlayer myPlayer = getMyPlayer();
		int anim = -1;

		while (System.currentTimeMillis() - start < timeout) {
			if ((anim = myPlayer.getAnimation()) != -1) {
				break;
			}
			wait(15); // As to not rape CPU.
		}
		return anim;
	}

	/**
	 * Waits the value of the <code>timeout</code> parameter, until the
	 * <code>RSIterface</code> in <code>iface</code> becomes "valid".<br />
	 * <br />
	 * <center>This Method uses <code>iface.isValid()</code> to stop.<br />
	 * </center> <br />
	 * If <code>iface</code> is <code>null</code> the method will return false.
	 * 
	 * @param iface
	 *            The RSInterface to wait until its valid.
	 * @param timeout
	 *            The time in MilliSeconds to wait for the RSInterface.
	 * @return True - if <code>iface</code> is valid before <code>timeout</code>
	 *         expires.<br />
	 *         False - if <code>timeout</code> expires before <code>iface</code>
	 *         is valid.<br />
	 *         False - if <code>iface</code> is <code>null</code>.<br />
	 *         False - if <code>timeout</code> is lesser than 0.
	 * @since 1.0 - 08-03-2009
	 * @author pwnaz0r
	 */
	public boolean waitForIface(final RSInterface iface, final int timeout) {
		if (timeout < 0) {
			return false;
		}

		if (iface == null) {
			return false;
		}

		final long startTime = System.currentTimeMillis();

		while (System.currentTimeMillis() - startTime <= timeout) {
			if (iface.isValid()) {
				return true;
			}
			wait(125); // As to keep CPU usage low.
		}

		return false;
	}

	/**
	 * Waits the value of the <code>timeout</code> parameter, until the
	 * <code>RSInterfaceChild</code> in <code>iface</code> becomes "valid".<br />
	 * <br />
	 * <center>This Method uses <code>iface.isValid()</code> to stop.<br />
	 * </center> <br />
	 * If <code>iface</code> is <code>null</code> the method will return false.
	 * 
	 * @param iface
	 *            The RSInterfaceChild to wait until its valid.
	 * @param timeout
	 *            The time in MilliSeconds to wait for the RSInterface.
	 * @return True - if <code>iface</code> is valid before <code>timeout</code>
	 *         expires.<br />
	 *         False - if <code>timeout</code> expires before <code>iface</code>
	 *         is valid.<br />
	 *         False - if <code>iface</code> is <code>null</code>.<br />
	 *         False - if <code>timeout</code> is lesser than 0.
	 * @since 1.0 - 08-03-2009
	 * @author pwnaz0r
	 */
	public boolean waitForIface(final RSInterfaceChild iface, final int timeout) {
		if (timeout < 0) {
			return false;
		}

		if (iface == null) {
			return false;
		}

		final long startTime = System.currentTimeMillis();

		while (System.currentTimeMillis() - startTime <= timeout) {
			if (iface.isValid()) {
				return true;
			}
			wait(125); // As to keep CPU usage low.
		}

		return false;
	}

	/**
	 * Waits up to timeout millis to start moving. This will return the instant
	 * movement starts. You can handle waiting a random amount afterwards by
	 * yourself.
	 * 
	 * @param timeout
	 *            Maximum time to wait to start moving (in milliseconds)
	 * @return True if we started moving, false if we reached the timeout.
	 */
	public boolean waitToMove(final int timeout) {
		final long start = System.currentTimeMillis();
		final RSPlayer myPlayer = getMyPlayer();

		while (System.currentTimeMillis() - start < timeout) {
			if (myPlayer.isMoving()) {
				return true;
			}
			wait(15); // As to not rape CPU.
		}
		return false;
	}

	/**
	 * Temporary - might not exist for long.
	 */
	public boolean walkPathKillaTemp(final RSTile[] path, final int maxDist) {
		try {
			final RSTile next = nextTile(path, maxDist);
			if (next != null && !next.equals(Methods.current)
					&& distanceTo(Methods.current) <= 2) {
				walkTileMM(next);
				Methods.current = next;
				return false;
			} else if (next != null && next.equals(Methods.current)) {
				return false;
			}
		} catch (final Exception e) {
			return false;
		}
		return true;
	}

	public boolean walkPathMM(final RSTile[] path, final int maxDist) {
		try {
			final RSTile next = nextTile(path, maxDist);
			if (next != null && !next.equals(Methods.current)) {
				walkTileMM(next);
				Methods.current = next;
				return false;
			} else if (next != null && next.equals(Methods.current)) {
				return false;
			}
		} catch (final Exception e) {
			return false;
		}
		return true;
	}

	public boolean walkTileMM(final RSTile t) {
		final Point p = tileToMinimap(t);
		if (p.x == -1 || p.y == -1) {
			return false;
		}
		clickMouse(p, 2, 2, true);
		return true;
	}

	public boolean walkTo(final int x, final int y) {
		final RSTile[] path = generateProperPath(x, y);
		if (path == null) {
			return false;
		}
		while (true) {
			if (!getMyPlayer().isMoving() || distanceTo(getDestination()) > 4) {
				if (walkPathMM(randomizePath(path, 2, 2), 17)) {
					return true;
				}
			}
		}
	}

	public boolean walkTo(final RSTile t) {
		return walkTo(t.getX(), t.getY());
	}

	/**
	 * @deprecated Use {@link com.speljohan.rsbot.script.Bank} instead
	 */
	@Deprecated
	public boolean withdraw(final int itemID, final int count) {
		return bank.withdraw(itemID, count);
	}

	public Point worldToMinimap(final int x, final int y) {
		return Calculations.worldToMinimap(x, y);
	}
}